#include "memory/cache.h"
#include <stdlib.h>
#include "burst.h"
#include <time.h>

void _ddr3_read(hwaddr_t addr, void *data);
void dram_write(hwaddr_t addr, size_t len, uint32_t data);
void _ddr3_write(hwaddr_t addr, void *data, uint8_t *mask);

void init_cache() {
    int i;
    for (i = 0; i < CACHE_1_SET_SIZE * CACHE_1_WAY_SIZE; i ++) {
        cache1[i].valid_bit = false;
    }
}

uint32_t cache1_read(hwaddr_t addr, size_t len) {
    uint32_t offset = addr & (CACHE_1_BLOCK_SIZE - 1);
    uint32_t group_index = (addr >> CACHE_1_BLOCK_BITS) & (CACHE_1_SET_SIZE - 1);
    uint32_t tag = (addr >> (CACHE_1_BLOCK_BITS + CACHE_1_SET_BITS));
    int i;
    int start = group_index * CACHE_1_WAY_SIZE;
    uint8_t data[4];
    for (i = start; i < start + CACHE_1_WAY_SIZE; i ++) {
        if (cache1[i].valid_bit && cache1[i].tag == tag) {
            // hit
            break;
        }
    }
    if (i == start + CACHE_1_WAY_SIZE) {
        // miss
        srand(time(0));
        i = start + rand() % CACHE_1_WAY_SIZE;
        // cache2 read
        memcpy(cache1[i].data, cache2[cache2_read(addr)].data, CACHE_1_BLOCK_SIZE);
        // uint32_t block_addr = (addr >> CACHE_1_BLOCK_BITS) << CACHE_1_BLOCK_BITS;
        // int j;
        // for (j = 0; j < CACHE_1_BLOCK_SIZE; j += BURST_LEN) {
        //     _ddr3_read(block_addr + j, cache1[i].data + j);
        // }
        cache1[i].valid_bit = true;
        cache1[i].tag = tag;
    }

    if (offset + len > CACHE_1_BLOCK_SIZE) {
        // cross block
        memcpy(data, cache1[i].data + offset, CACHE_1_BLOCK_SIZE - offset);
        uint32_t remain_data = cache1_read(addr + CACHE_1_BLOCK_SIZE - offset, len - (CACHE_1_BLOCK_SIZE - offset));
        memcpy(data + CACHE_1_BLOCK_SIZE - offset, &remain_data, len - (CACHE_1_BLOCK_SIZE - offset));
    } else {
        // same block
        memcpy(data, cache1[i].data + offset, len);
    }
    uint32_t zero = 0;
    return unalign_rw(data + zero, 4) & (~0u >> ((4 - len) << 3));
}

void cache1_write(hwaddr_t addr, size_t len, uint32_t data) {
    uint32_t offset = addr & (CACHE_1_BLOCK_SIZE - 1);
    uint32_t group_index = (addr >> CACHE_1_BLOCK_BITS) & (CACHE_1_SET_SIZE - 1);
    uint32_t tag = (addr >> (CACHE_1_BLOCK_BITS + CACHE_1_SET_BITS));

    int i;
    int start = group_index * CACHE_1_WAY_SIZE;
    for (i = start; i < start + CACHE_1_WAY_SIZE; i ++) {
        if (cache1[i].valid_bit && cache1[i].tag == tag) {
            // hit
            break;
        }
    }
    if (i == start + CACHE_1_WAY_SIZE) {
        // cache2 write or dram write
        srand(time(0));
        i = start + rand() % CACHE_1_WAY_SIZE;
        cache2_write(addr, len, data);
        cache1[i].valid_bit = true;
        cache1[i].tag = tag;
    }

    if (offset + len > CACHE_1_BLOCK_SIZE) {
        // cross block
        memcpy(cache1[i].data + offset, &data, CACHE_1_BLOCK_SIZE - offset);
        dram_write(addr, CACHE_1_BLOCK_SIZE - offset, data);
        cache2_write(addr, CACHE_1_BLOCK_SIZE - offset, data);

        cache1_write(addr + CACHE_1_BLOCK_SIZE - offset, len - (CACHE_1_BLOCK_SIZE - offset), data >> (8 * (CACHE_1_BLOCK_SIZE - offset)));
    } else {
        // same block
        memcpy(cache1[i].data + offset, &data, len);
        dram_write(addr, len, data);
        cache2_write(addr, len, data);
    }
}

uint32_t cache2_read(hwaddr_t addr) {
    uint32_t group_index = (addr >> CACHE_2_BLOCK_BITS) & (CACHE_2_SET_SIZE - 1);
    uint32_t tag = (addr >> (CACHE_2_BLOCK_BITS + CACHE_2_SET_BITS));

    int i;
    int start = group_index * CACHE_2_WAY_SIZE;
    for (i = start; i < start + CACHE_2_WAY_SIZE; i ++) {
        if (cache2[i].valid_bit && cache2[i].tag == tag) {
            // hit
            return i;
        }
    }
    // miss
    srand(time(0));
    i = start + rand() % CACHE_2_WAY_SIZE;
    if (cache2[i].valid_bit && cache2[i].dirty_bit) {
        // write back
        uint8_t mask[BURST_LEN * 2];
        memset(mask, 1, BURST_LEN * 2);
        uint32_t block_addr = (cache2[i].tag << (CACHE_2_BLOCK_BITS + CACHE_2_SET_BITS)) | (group_index << CACHE_2_BLOCK_BITS);
        int j;
        for (j = 0; j < CACHE_2_BLOCK_SIZE; j += BURST_LEN) {
            _ddr3_write(block_addr + j, cache2[i].data + j, mask);
        }
    }
    // read from dram
    int j;
    uint32_t block_addr = (addr >> CACHE_2_BLOCK_BITS) << CACHE_2_BLOCK_BITS;
    for (j = 0; j < CACHE_2_BLOCK_SIZE; j += BURST_LEN) {
        _ddr3_read(block_addr + j, cache2[i].data + j);
    }
    cache2[i].valid_bit = 1;
    cache2[i].dirty_bit = 0;
    cache2[i].tag = tag;
    return i;
}

void cache2_write(hwaddr_t addr, size_t len, uint32_t data) {
    uint32_t offset = addr & (CACHE_2_BLOCK_SIZE - 1);
    uint32_t group_index = (addr >> CACHE_2_BLOCK_BITS) & (CACHE_2_SET_SIZE - 1);
    uint32_t tag = (addr >> (CACHE_2_BLOCK_BITS + CACHE_2_SET_BITS));

    int i;
    int start = group_index * CACHE_2_WAY_SIZE;
    for (i = start; i < start + CACHE_2_WAY_SIZE; i ++) {
        if (cache2[i].valid_bit && cache2[i].tag == tag) {
            // hit
            break;
        }
    }
    if (i == start + CACHE_2_WAY_SIZE) {
        i = cache2_read(addr);
    }
    cache2[i].dirty_bit = 1;
    if (offset + len > CACHE_2_BLOCK_SIZE) {
        // cross block
        memcpy(cache2[i].data + offset, &data, CACHE_2_BLOCK_SIZE - offset);
        cache2_write(addr + CACHE_2_BLOCK_SIZE - offset, len - (CACHE_2_BLOCK_SIZE - offset), data >> (8 * (CACHE_2_BLOCK_SIZE - offset)));
    } else {
        // same block
        memcpy(cache2[i].data + offset, &data, len);
    }
    return;

}